use std::fmt::{Display, Formatter};
use std::iter::Peekable;
use std::str::Chars;
use error::Result;
use crate::error;
use crate::error::Error;

//词法单元
#[derive(Debug, Clone, PartialEq)]
pub enum Token {
    //关键字
    Keyword(Keyword),
    //标识符
    Ident(String),
    //字符串
    String(String),
    //数值
    Number(String),
    //左括号
    OpenParen,
    //右括号
    CloseParen,
    //逗号
    Comma,
    //分号
    Semicolon,
    //星 *
    Asterisk,
    //加 +
    Plus,
    //减 -
    Minus,
    //斜杠 /
    Slash,
}
impl Display for Token {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.write_str(match self {
            Token::Keyword(keyword) => keyword.to_str(),
            Token::Ident(ident) => ident,
            Token::String(s) => s,
            Token::Number(n) => n,
            Token::OpenParen => "(",
            Token::CloseParen => ")",
            Token::Comma => ",",
            Token::Semicolon => ";",
            Token::Asterisk => "*",
            Token::Plus => "+",
            Token::Minus => "-",
            Token::Slash => "/"
        })
    }
}
//关键字
#[derive(Debug, Clone, PartialEq)]
pub enum Keyword {
    Create, Table, Int, Integer, Boolean, Bool, String, Text, Varchar, Float, Double, Select, From,
    Insert, Into, Values, True, False, Default, Not, Null, Primary, Key
}

impl Keyword {
    //字符串转换成关键字枚举
    pub fn str_to_convert(k: &str) -> Option<Self> {
        Some(match k.to_uppercase().as_ref() {
            "CREATE" => Keyword::Create,
            "TABLE" => Keyword::Table,
            "INT" => Keyword::Int,
            "INTEGER" => Keyword::Integer,
            "BOOLEAN" => Keyword::Boolean,
            "BOOL" => Keyword::Bool,
            "STRING" => Keyword::String,
            "TEXT" => Keyword::Text,
            "VARCHAR" => Keyword::Varchar,
            "FLOAT" => Keyword::Float,
            "DOUBLE" => Keyword::Double,
            "SELECT" => Keyword::Select,
            "FROM" => Keyword::From,
            "INSERT" => Keyword::Insert,
            "INTO" => Keyword::Into,
            "VALUES" => Keyword::Values,
            "TRUE" => Keyword::True,
            "FALSE" => Keyword::False,
            "DEFAULT" => Keyword::Default,
            "NOT" => Keyword::Not,
            "NULL" => Keyword::Null,
            "PRIMARY" => Keyword::Primary,
            "KEY" => Keyword::Key,
            //如果没有匹配到，则返回空
            _ => return None
        })
    }
    pub fn to_str(&self) -> &str {
        match self {
            Keyword::Create => "CREATE",
            Keyword::Table => "TABLE",
            Keyword::Int => "INT",
            Keyword::Integer => "INTEGER",
            Keyword::Boolean => "BOOLEAN",
            Keyword::Bool => "BOOL",
            Keyword::String => "STRING",
            Keyword::Text => "TEXT",
            Keyword::Varchar => "VARCHAR",
            Keyword::Float => "FLOAT",
            Keyword::Double => "DOUBLE",
            Keyword::Select => "SELECT",
            Keyword::From => "FROM",
            Keyword::Insert => "INSERT",
            Keyword::Into => "INTO",
            Keyword::Values => "VALUES",
            Keyword::True => "TRUE",
            Keyword::False => "FALSE",
            Keyword::Default => "DEFAULT",
            Keyword::Not => "NOT",
            Keyword::Null => "NULL",
            Keyword::Primary => "PRIMARY",
            Keyword::Key => "KEY",
        }
    }
}

impl Display for Keyword {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        f.write_str(self.to_str())
    }
}

pub struct Lexer<'a> {
    //读取sql，使用Peekable，可以窥探下一个字符
    iter: Peekable<Chars<'a>>
}

//自定义迭代器，返回Token
impl<'a> Iterator for Lexer<'a> {
    type Item = Result<Token>;

    fn next(&mut self) -> Option<Self::Item> {
        match self.scan() {
            Ok(Some(token)) => Some(Ok(token)),
            Ok(None) => self.iter.peek().map(|_c| Err(Error::Parse(String::from("解析错误，未找到对应的词法单元")))),
            Err(e) => Some(Err(e))
        }
    }
}

impl<'a> Lexer<'a> {
    //结构体的new - 创建结构体
    pub fn new(sql: &'a str) -> Self {
        Self {
            iter: sql.chars().peekable()
        }
    }

    //扫描
    fn scan(&mut self) -> Result<Option<Token>> {
        loop {
            //窥探下一个字符
            match self.iter.peek() {
                Some(' ' | '\t' | '\r' | '\n') => {
                    //消费这个字符，并跳过
                    self.iter.next();
                    continue
                },
                //单引号 - 字符串的开始符号
                Some('\'') => return self.scan_string(),
                //数值
                Some(c) if c.is_ascii_digit() => return Ok(self.scan_number()),
                //标识符或关键字 - 以字母开头
                Some(c) if c.is_alphabetic()=> return Ok(self.scan_ident()),
                //符号
                Some(_) => return Ok(self.scan_symbol()),
                None => return Ok(None),
            }
        }
    }

    //窥探下个字符，若满足条件，则消费并返回下个字符
    fn scan_next<F: Fn(char) -> bool>(&mut self, predicate: F) -> Option<char> {
        self.iter.peek().filter(|&c| predicate(*c))?;
        self.iter.next()
    }

    //扫描字符串
    fn scan_string(&mut self) -> Result<Option<Token>> {
        //单引号开头，读取开头的单引号
        self.iter.next();
        //不断读取单引号中的内容
        let mut val: String = String::new();
        loop {
            match self.iter.next() {
                //如果又读取到了单引号，说明单引号闭合
                Some('\'') => break,
                //单引号中的内容，存入字符串
                Some(c) => val.push(c),
                //解析字符串错误
                None => return Err(Error::Parse(String::from("解析SQL中的字符串错误")))
            }
        }
        //返回字符串
        Ok(Some(Token::String(val)))
    }

    //扫描数值
    fn scan_number(&mut self) -> Option<Token> {
        let mut val: String = String::new();
        //消耗第一个数字字符
        let first_number = self.iter.next()?;
        val.push(first_number);

        //如果下一个字符是数字，就一直读取
        while let Some(c) = self.scan_next(|c| c.is_ascii_digit()) {
            val.push(c);
        }

        //读取完数字，如果下一个字符是 . 说明是小数
        if let Some(c) = self.scan_next(|c| c == '.') {
            //将 . 添加
            val.push(c);
            //继续向后读取小数点后的数字
            while let Some(c) = self.scan_next(|c| c.is_ascii_digit()) {
                val.push(c);
            }
        }

        //返回数值字符串
        Some(Token::Number(val))
    }

    //扫描标识符 或 关键字
    fn scan_ident(&mut self) -> Option<Token> {
        let mut val: String = String::new();
        //消耗第一个字符
        let first_string = self.iter.next()?;
        val.push(first_string);

        //后续字符，如果是字符 或 数字 或 下划线
        while let Some(c) = self.scan_next(|c| c.is_alphanumeric()|| c == '_') {
            val.push(c);
        }

        let result = Keyword::str_to_convert(&val).map_or(Token::Ident(val.to_lowercase()), Token::Keyword);
        Some(result)
    }

    //扫描符号
    fn scan_symbol(&mut self) -> Option<Token> {
        self.next_if_token(|c|
            match c {
                '*' => return Some(Token::Asterisk),
                '(' => return Some(Token::OpenParen),
                ')' => return Some(Token::CloseParen),
                ',' => return Some(Token::Comma),
                ';' => return Some(Token::Semicolon),
                '+' => return Some(Token::Plus),
                '-' => return Some(Token::Minus),
                '/' => return Some(Token::Slash),
                _ => None,
            }
        )
    }

    //只有是Token类型，才跳转到下一个，并返回Token
    fn next_if_token<F: Fn(char) -> Option<Token>>(&mut self, predicate: F) -> Option<Token> {
        let token = self.iter.peek().and_then(|c| predicate(*c))?;
        self.iter.next();
        Some(token)
    }

}


#[cfg(test)]
mod tests {
    use crate::error::Result;
    use crate::sql::parse::lexer::Lexer;

    #[test]
    fn test_lexer_create_table() -> Result<()> {
        let tokens = Lexer::new(
            "create table tbl (
                id1 int primary key,
                id2 integer
            );"
        ).peekable().collect::<Result<Vec<_>>>()?;

        println!("{:?}", tokens);

        Ok(())
    }

    #[test]
    fn test_lexer_insert() -> Result<()> {
        let tokens = Lexer::new(
            "insert into tbl values (1, 2, '3', true, false, 4.55);"
        ).peekable().collect::<Result<Vec<_>>>()?;

        println!("{:?}", tokens);

        Ok(())
    }

}